// This file is part of Keepass2Android, Copyright 2025 Philipp Crocoll.
//
//   Keepass2Android is free software: you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   Keepass2Android is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with Keepass2Android.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.IO;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using KeePassLib.Serialization;

namespace keepass2android.Io
{

  public enum FileStorageResults
  {
    FullFilenameSelected = 874345 + 1,
    FileChooserPrepared = FullFilenameSelected + 1,
    FileUsagePrepared = FileChooserPrepared + 1
  }

  public class OptionalOut<T>
  {
    public T Result { get; set; }
  }

  public static class FileStorageSetupDefs
  {
    public static String ProcessNameSelectfile = "SELECT_FILE";
    public static String ProcessNameFileUsageSetup = "FILE_USAGE_SETUP";

    public static String ExtraProcessName = "EXTRA_PROCESS_NAME";
    public static String ExtraAlwaysReturnSuccess = "EXTRA_ALWAYS_RETURN_SUCCESS";
    public static String ExtraPath = "fileName"; //match KP2A PasswordActivity Ioc-Path Extra key
    public static String ExtraIsForSave = "IS_FOR_SAVE";
    public static String ExtraErrorMessage = "EXTRA_ERROR_MESSAGE";

  }

  /// <summary>
  /// Interface to encapsulate all access to disk or cloud.
  /// </summary>
  /// Note that it was decided to use the IOConnectionInfo also for cloud storage.
  /// The advantage is that the database for saving recent files etc. will then work without 
  /// much work to do. Furthermore, the IOConnectionInfo seems generic info to capture all required data, even though it might be nicer to 
  /// have an IIoStorageId interface in few cases.*/
  public interface IFileStorage
  {
    /// <summary>
    /// returns the protocol ids supported by this FileStorage. Can return pseudo-protocols like "dropbox" or real protocols like "ftp"
    /// </summary>
    IEnumerable<string> SupportedProtocols { get; }

    /// <summary>
    /// returns true if users should backup files on this file storage (if the file is important). Can be false for cloud providers with built-in versioning or backups.
    /// </summary>
    bool UserShouldBackup { get; }

    /// <summary>
    /// Deletes the given file or directory.
    /// </summary>
    void Delete(IOConnectionInfo ioc);

    /// <summary>
    /// Tests whether the file was changed. 
    /// </summary>
    /// Note: This function may return false even if the file might have changed. The function
    /// should focus on being fast and cheap instead of doing things like hashing or downloading a full file.
    /// previousFileVersion may be null to indicate no previous version is known.
    /// <returns>Returns true if a change was detected, false otherwise.</returns>
    bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion);

    /// <summary>
    /// Returns a string describing the "version" of the file specified by ioc.
    /// </summary>
    /// This string may have a deliberate value (except null) and should not be used by callers except for passing it to
    /// CheckForFileChangeFast().
    /// <returns>A string describing the version. Null means, there is no way to get a file version (or it's not implemented).</returns>
    string GetCurrentFileVersionFast(IOConnectionInfo ioc);

    /// <summary>
    /// Opens the given file for reading
    /// </summary>
    Stream OpenFileForRead(IOConnectionInfo ioc);

    /// <summary>
    /// Opens a write transaction for writing to the given ioc. 
    /// </summary>
    /// <param name="ioc">ioc to write to</param>
    /// <param name="useFileTransaction">if true, force to use file system level transaction. This might be ignored if the file storage has built in transaction support</param>
    IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction);

    string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc);

    string GetFileExtension(IOConnectionInfo ioc);

    /// <summary>
    /// Returns true if the the given ioc must be filled with username/password
    /// </summary>
    bool RequiresCredentials(IOConnectionInfo ioc);

    /// <summary>
    /// Creates the directory described by ioc
    /// </summary>
    void CreateDirectory(IOConnectionInfo ioc, string newDirName);

    /// <summary>
    /// Lists the contents of the given path
    /// </summary>
    IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc);

    /// <summary>
    /// returns the description of the given file
    /// </summary>
    FileDescription GetFileDescription(IOConnectionInfo ioc);

    /// <summary>
    /// returns true if everything is ok with connecting to the given file. 
    /// Returns False if PrepareFileUsage must be called first.
    /// </summary>
    bool RequiresSetup(IOConnectionInfo ioConnection);

    /// <summary>
    /// converts the ioc to a path which may contain the credentials
    /// </summary>
    string IocToPath(IOConnectionInfo ioc);

    /// <summary>
    /// Initiates the process for choosing a file in the given file storage.
    /// The file storage should either call OnImmediateResult or StartSelectFileProcess
    /// </summary>
    void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId);

    /// <summary>
    /// Initiates the process for using a file in the given file storage.
    /// The file storage should either call OnImmediateResult or StartFileUsageProcess
    /// If alwaysReturnSuccess is true, the activity should be finished with ResultCode Ok.
    /// This can make sense if a higher-level file storage has the file cached but still wants to 
    /// give the cached storage the chance to initialize file access.
    /// </summary>
    void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, bool alwaysReturnSuccess);

    /// <summary>
    /// Initiates the process for using a file in the given file storage.
    /// This method either silently prepares using the file (if any preparation is required) or throws
    /// UserInteractionRequiredException (or any other exception in case of an error). 
    /// Can be used from a service, i.e. when no Activity is open.
    /// </summary>
    void PrepareFileUsage(Context ctx, IOConnectionInfo ioc);

    //Setup methods: these are called from the setup activity so the file storage can handle UI events for authorization etc.
    void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState);
    void OnResume(IFileStorageSetupActivity activity);
    void OnStart(IFileStorageSetupActivity activity);
    void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data);

    /// <summary>
    /// Converts the given path to a displayable string
    /// </summary>
    string GetDisplayName(IOConnectionInfo ioc);

    //returns the path of a file "newFilename" in the folder "parent"
    //this may create the file if this is required to get a path (if a UUID is part of the file path)
    string CreateFilePath(string parent, string newFilename);

    /// <summary>
    /// returns the parent folder of ioc
    /// </summary>
    IOConnectionInfo GetParentPath(IOConnectionInfo ioc);

    /// <summary>
    /// returns the file path of the file "filename" in the folderPath.
    /// </summary>
    /// The method may throw FileNotFoundException or not in case the file doesn't exist.
    IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename);

    /// <summary>
    /// returns true if it can be expected that this location will be available permanently (in contrast to a cache copy or temporary URI permissions in Android)
    /// </summary>
    /// Does not require to exist forever!
    bool IsPermanentLocation(IOConnectionInfo ioc);



    /// <summary>
    /// Should return true if the file cannot be written.
    /// </summary>
    bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null);

  }

  public interface IPermissionRequestingFileStorage
  {
    void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode, string[] permissions, Permission[] grantResults);
  }

  public interface IWriteTransaction : IDisposable
  {
    Stream OpenFile();
    void CommitWrite();
  }

  public class FileStorageSelectionInfo
  {
    public enum FileStorageSelectionMessageType
    {
      Info,  //show only ok button
      CancellableInfo, //show Ok/Cancel
      Error //show cancel only
    }

    public UiStringKey SelectionMessage { get; set; }
    public FileStorageSelectionMessageType MessageType { get; set; }
  }

  /// <summary>
  /// Can be implemented by IFileStorage implementers to add additional information for the 
  /// process of selecting the file storage
  /// </summary>
  public interface IFileStorageSelectionInfoProvider
  {
    FileStorageSelectionInfo TryGetSelectionInfo(string protocolId);
  }
}